package com.entity

import com.Crc32
import com.FileCrcMode
import com.FileCrcMode.*
import extend.applyIf
import java.io.*
import java.nio.charset.Charset
import java.nio.file.LinkOption
import java.nio.file.OpenOption
import java.nio.file.Path
import java.nio.file.attribute.FileAttribute
import java.nio.file.attribute.FileTime
import kotlin.io.path.*

/**
 * 打包路径处理类
 * 用于处理和转换打包过程中的文件路径
 */
class PackPath() {

    companion object {

        /**
         * 格式化路径并创建PackPath对象
         *
         * 该方法根据路径字符串的第一个字符判断是否需要进行绝对路径转换和标准化处理。
         * 对于以相对路径符号(., ..)或路径分隔符(/, \)开头的路径，会进行绝对化和标准化处理。
         * 其他路径则直接创建PackPath对象，保持原始路径格式。
         *
         * 示例：
         *
         * ```
         * // 处理相对路径
         * val path1 = PackPath.format("./documents/file.txt", "/home/user")
         * // 结果路径: /home/user/documents/file.txt (经过绝对化和标准化)
         *
         * // 处理绝对路径
         * val path2 = PackPath.format("/home/user/../user/documents/./file.txt")
         * // 结果路径: /home/user/documents/file.txt (经过绝对化和标准化)
         *
         * // 处理普通路径
         * val path3 = PackPath.format("documents/file.txt")
         * // 结果路径: documents/file.txt (保持原始格式)
         * ```
         *
         * @param path 要格式化的路径字符串，可以是相对路径或绝对路径
         * @param base 基础路径，默认为空字符串，当path为相对路径时会基于此路径进行解析
         * @return 返回处理后的PackPath对象
         *
         */
        fun format(path: String, base: String = ""): PackPath {
            val packPath = PackPath(path)
            if (!packPath.isAbsolute) {
                packPath.replaceDirectory(base)
                packPath.absolutePath()
                packPath.normalize()
            }
            return packPath
        }

    }

    private lateinit var path: String

    private lateinit var currentPath: Path

    /**
     * 构造函数，使用基础路径和子路径构建完整路径
     *
     * @param base 基础路径
     * @param subpaths 子路径列表
     *
     * 示例：
     *```
     * val path1 = PackPath("/home/user", "documents", "file.txt")
     * // 结果路径: /home/user/documents/file.txt
     *
     * val path2 = PackPath("C:\Users", "Admin", "Desktop", "test.txt")
     * // 结果路径: C:\Users\Admin\Desktop\test.txt
     * ```
     */
    constructor(base: String, vararg subpaths: String) : this(Path(base, *subpaths))

    constructor(path: String) : this() {
        this.path = path
        currentPath = Path(path)
    }

    constructor(base: Path) : this() {
        currentPath = base
        this.path = base.pathString
    }

    /**
     * 返回 父路径，或者 null 如果此路径没有父路径。
     *
     * 示例：
     * ```
     * val path = PackPath("/home/user/documents/file.txt")
     * val parent = path.parent
     * // parent表示 /home/user/documents
     * ```
     */
    val parent: PackPath?
        get() = currentPath.parent.let {
            if (it != null)
                PackPath(it.pathString)
            else it
        }

    /**
     * 获取路径的字符串表示，并将所有反斜杠替换为正斜杠。
     *
     * 这样可以确保路径在不同操作系统上的兼容性。
     *
     * 示例：
     * ```
     * val path = PackPath("C:\\Users\\Documents\\file.txt")
     * println(path.pathString)
     * // 输出: C:/Users/Documents/file.txt
     * ```
     */
    val pathString: String
        get() = currentPath.pathString.applyIf({
            it.contains("\\")
        }) {
            it.replace("\\", "/")
        }

    /**
     * 获取绝对路径的字符串表示，并将所有反斜杠替换为正斜杠。
     *
     * 这样可以确保路径在不同操作系统上的兼容性。
     * ```
     * val packPath = PackPath("file.txt")
     * println(packPath.absolutePath) // 输出: /current/working/directory/file.txt
     * ```
     *
     * 示例：
     * ```
     * val path = PackPath("documents/file.txt")
     * println(path.absolutePath)
     * // 输出: /home/user/documents/file.txt (在Linux/Mac上)
     * // 或者 C:/Users/YourName/documents/file.txt (在Windows上)
     * ```
     */
    val absolutePathString: String
        get() = currentPath.absolutePathString()
            .applyIf({
                it.contains("\\")
            }) {
                it.replace("\\", "/")
            }

    /**
     * 告诉此路径是否为绝对路径。
     * 绝对路径是完整的，因为它不需要与其他路径信息组合即可找到文件
     *
     * 示例：
     * ```
     * val absolutePath = PackPath("/home/user/file.txt")
     * val relativePath = PackPath("documents/file.txt")
     * println(absolutePath.isAbsolute)  // 输出: true
     * println(relativePath.isAbsolute)  // 输出: false
     * ```
     */
    val isAbsolute: Boolean
        get() = currentPath.isAbsolute

    /**
     * 获取原始传入的路径字符串
     *
     * 示例：
     * ```
     * val path = PackPath("/home/user/documents/file.txt")
     * println(path.original)
     * // 输出: /home/user/documents/file.txt
     * ```
     */
    val original: String
        get() = path

    /**
     * 获取当前Path对象
     *
     * 示例：
     * ```
     * val packPath = PackPath("/home/user/file.txt")
     * val path = packPath.toPath
     * // path是java.nio.file.Path对象
     * ```
     */
    val toPath: Path
        get() = currentPath

    /**
     * 返回该路径的根组件作为Path对象，如果此路径没有根组件，则将其返回为null 。
     * @return 表示该路径的根部分量或null路径
     */
    val root: Path
        get() = currentPath.root

    /**
     * 规范化当前路径
     *
     * 此函数将当前路径转换为规范形式，移除冗余的路径元素（如 "." 和 ".."）
     * 并更新当前路径实例
     *
     * 示例：
     * ```
     * val path = PackPath("/home/user/../user/documents/./file.txt")
     * path.normalize()
     * println(path.pathString)
     * // 输出: /home/user/documents/file.txt
     * ```
     */
    fun normalize() {
        currentPath = currentPath.normalize()
    }

    /**
     * 创建规范化路径的新实例
     *
     * @return 返回一个新的 PackPath 实例，其中包含当前路径的规范化版本
     *
     * 示例：
     * ```
     * val path = PackPath("/home/user/../user/documents/./file.txt")
     * val normalizedPath = path.toNormalize()
     * println(normalizedPath.pathString)
     * // 输出: /home/user/documents/file.txt
     * ```
     */
    fun toNormalize(): PackPath {
        return PackPath(currentPath.normalize())
    }

    /**
     * 替换当前路径
     * @param newPath 新的路径字符串
     *
     * 示例：
     * ```
     * val path = PackPath("/home/user/file.txt")
     * path.replacePath("/documents/report.pdf")
     * println(path.pathString)
     * // 输出: /documents/report.pdf
     * ```
     */
    fun replacePath(newPath: String) {
        currentPath = Path(newPath)
    }

    /**
     * 替换当前路径对象为新的路径对象
     *
     * 此方法会将当前 PackPath 实例内部的路径引用替换为传入的 newPath 对象的路径引用。
     * 执行此操作后：
     * 1. 当前路径对象将共享 newPath 对象的路径数据（即 currentPath = newPath.toPath）
     * 2. 当前路径对象原有的路径数据会被完全覆盖和丢弃
     * 3. 该操作仅修改内存中的路径引用，不会对文件系统进行任何实际操作
     *
     * 注意：执行此操作后，当前对象和 newPath 对象将引用同一个 Path 实例，
     * 对其中一个对象的进一步修改（如果 Path 是可变的）可能会影响另一个对象。
     *
     * @param newPath 新的 PackPath 对象，其路径将替换并覆盖当前路径
     *
     */
    fun replacePath(newPath: PackPath) {
        currentPath = newPath.toPath
    }

    /**
     * 替换当前路径的目录部分，保持文件名不变
     * @param newDirectory 新的目录路径字符串
     *
     * 示例：
     * ```
     * val path = PackPath("/home/user/file.txt")
     * path.replaceDirectory("/documents")
     * println(path.pathString)
     * // 输出: /documents/file.txt
     * ```
     */
    fun replaceDirectory(newDirectory: String) {
        currentPath = Path(newDirectory).resolve(currentPath)
    }

    /**
     * 根据指定的CRC32校验值和模式修改文件名
     * @param crc32 CRC32校验值字符串
     * @param mode 文件名修改模式，决定CRC32值如何添加到文件名中
     *
     * 示例：
     * ```
     * val path = PackPath("/home/user/file.txt")
     * path.renameWithCrc32("abcd1234", FileCrcMode.APPEND)
     * println(path.pathString)
     * // 输出: /home/user/file_abcd1234.txt
     * ```
     */
    fun renameWithCrc32(crc32: String, mode: FileCrcMode) {
        when (mode) {
            // 根据不同模式修改文件名
            APPEND -> renameTo("${nameWithoutExtension}-$crc32.$extension", true)
            PREFIX -> renameTo("$crc32-$name", true)
            FULL -> renameTo("$crc32.$extension", true)
            NONE -> {}
        }
    }

    /**
     * 获取文件扩展名
     * @return 文件扩展名字符串
     *
     * 示例：
     * ```
     * val path = PackPath("/home/user/document.pdf")
     * println(path.extension)
     * // 输出: pdf
     * ```
     */
    val extension: String
        get() = currentPath.extension


    /**
     * 获取不包含扩展名的文件名
     * @return 不包含扩展名的文件名字符串
     *
     * 示例：
     * ```
     * val path = PackPath("/home/user/document.pdf")
     * println(path.nameWithoutExtension)
     * // 输出: document
     * ```
     */
    val nameWithoutExtension: String
        get() = currentPath.nameWithoutExtension

    /**
     * 获取文件名
     * @return 文件名字符串
     *
     * 示例：
     * ```
     * val path = PackPath("/home/user/document.pdf")
     * println(path.name)
     * // 输出: document.pdf
     * ```
     */
    val name: String
        get() = currentPath.name

    /**
     * 检查文件是否存在
     * @param options 链接选项数组
     * @return 如果文件存在返回true，否则返回false
     */
    fun exists(vararg options: LinkOption): Boolean = currentPath.exists(*options)

    /**
     * 检查文件是否不存在
     * @param options 链接选项数组
     * @return 如果文件不存在返回true，否则返回false
     */
    fun notExists(vararg options: LinkOption): Boolean = currentPath.notExists(*options)

    /**
     * 从源路径移动文件到当前路径
     * @param sourcePath 源文件路径
     * @param overwrite 是否覆盖已存在的文件，默认为true
     * @return 移动成功返回true，失败返回false
     */
    fun moveFileFrom(sourcePath: PathUnionType, overwrite: Boolean = true): Boolean {
        return try {
            // 创建父级目录
            createParentDirectories()
            // 根据源路径类型执行移动操作
            if (sourcePath is PathUnionType.StringType) {
                PackPath(sourcePath.value).moveTo(this, overwrite)
            } else if (sourcePath is PathUnionType.PathType)
                sourcePath.value.moveTo(this, overwrite)
            true
        } catch (e: Exception) {
            false
        }
    }

    /**
     * 从源路径复制文件到当前路径
     * @param sourcePath 源文件路径
     * @param overwrite 是否覆盖已存在的文件，默认为true
     * @return 复制成功返回true，失败返回false
     */
    fun copyFileFrom(sourcePath: PathUnionType, overwrite: Boolean = true): Boolean {
        return try {
            // 创建父级目录
            createParentDirectories()
            // 根据源路径类型执行复制操作
            if (sourcePath is PathUnionType.StringType) {
                PackPath(sourcePath.value).copyTo(this, overwrite)
            } else if (sourcePath is PathUnionType.PathType)
                sourcePath.value.copyTo(this, overwrite)
            true
        } catch (e: Exception) {
            false
        }
    }

    /**
     * 获取文件输入流
     * @param options 打开选项数组
     * @return 文件输入流
     */
    fun inputStream(vararg options: OpenOption): InputStream {
        return currentPath.inputStream(*options)
    }

    /**
     * 获取文件输出流
     * @param options 打开选项数组
     * @return 文件输出流
     */
    fun outputStream(vararg options: OpenOption): OutputStream {
        return currentPath.outputStream(*options)
    }


    /**
     * 解析给定的字符串路径，返回一个新的PackPath对象
     * @param other 需要解析的字符串路径
     * @return 解析后的新PackPath对象
     *
     * 示例：
     * ```
     * val base = PackPath("/home/user")
     * val resolved = base.resolve("documents/file.txt")
     * println(resolved.pathString)
     * // 输出: /home/user/documents/file.txt
     * ```
     */
    fun resolve(other: String): PackPath {
        return currentPath.resolve(other).toPackPath
    }

    /**
     * 解析给定的PackPath对象，返回一个新的PackPath对象
     * @param other 需要解析的PackPath对象
     * @return 解析后的新PackPath对象
     *
     * 示例：
     * ```
     * val base = PackPath("/home/user")
     * val subPath = PackPath("documents/file.txt")
     * val resolved = base.resolve(subPath)
     * println(resolved.pathString)
     * // 输出: /home/user/documents/file.txt
     * ```
     */
    fun resolve(other: PackPath): PackPath {
        return currentPath.resolve(other.toPath).toPackPath
    }

    /**
     * 将当前路径移动到目标路径
     * @param target 目标路径
     * @param overwrite 是否覆盖目标路径，如果目标已存在
     */
    fun moveTo(target: PackPath, overwrite: Boolean = false) {
        currentPath.moveTo(target.toPath, overwrite)
    }

    /**
     * 将当前路径重命名为新名称
     *
     * 此方法会实际移动磁盘上的文件或目录到新的名称，并更新当前路径对象。
     * 如果目标名称已存在且 overwrite 为 false，则会抛出 FileAlreadyExistsException 异常。
     *
     * 注意事项：
     * 1. 此操作会修改磁盘上的实际文件或目录
     * 2. 如果需要保留原文件，应使用 [copyTo] 方法
     * 3. 重命名操作实际上是文件系统的移动操作
     *
     * 示例：
     * ```
     * // 重命名文件
     * val file = PackPath("/home/user/old_name.txt")
     * file.renameTo("new_name.txt")
     * println(file.pathString) // 输出: /home/user/new_name.txt
     *
     * // 覆盖已存在的文件
     * val file2 = PackPath("/home/user/document.txt")
     * file2.renameTo("existing.txt", overwrite = true)
     * ```
     * @param newName 新的文件或目录名称（不包含路径，仅为名称部分）
     * @param overwrite 是否覆盖已存在的同名文件或目录，默认为false
     * @throws FileAlreadyExistsException 当目标文件已存在且 overwrite 为 false 时抛出
     * @throws java.io.IOException 当IO操作失败时抛出
     * @see moveTo
     * @see copyTo
     */
    fun renameTo(newName: String, overwrite: Boolean = false) {
        val movePath = currentPath.parent.resolve(newName)
        currentPath.moveTo(movePath, overwrite)
        currentPath = movePath
    }

    /**
     * 将当前路径复制到目标路径
     * @param target 目标路径
     * @param overwrite 是否覆盖目标路径，如果目标已存在
     */
    fun copyTo(target: PackPath, overwrite: Boolean = false) {
        currentPath.copyTo(target.toPath, overwrite)
    }

    /**
     * 创建当前路径的父目录
     * @param attributes 创建目录时的文件属性
     * @return 创建后的路径
     */
    fun createParentDirectories(vararg attributes: FileAttribute<*>): Path {
        return currentPath.createParentDirectories(*attributes)
    }

    /**
     * 递归删除当前路径及其所有子项
     * 注意：此功能使用实验性API
     */
    @OptIn(ExperimentalPathApi::class)
    fun deleteRecursively() {
        currentPath.deleteRecursively()
    }

    /**
     * 创建当前路径的所有父目录
     * @param attributes 创建目录时的文件属性
     * @return 当前PackPath实例，支持链式调用
     */
    fun createDirectories(vararg attributes: FileAttribute<*>): PackPath {
        currentPath.createDirectories(*attributes)
        return this
    }

    /**
     * 设置当前路径的最后修改时间
     * @param value 要设置的最后修改时间
     * @return 当前PackPath实例，支持链式调用
     */
    fun setLastModifiedTime(value: FileTime): PackPath {
        currentPath.setLastModifiedTime(value)
        return this
    }

    /**
     * 读取当前路径文件的文本内容
     * @param charset 文本编码，默认为UTF-8
     * @return 文件的文本内容
     */
    fun readText(charset: Charset = Charsets.UTF_8): String {
        return currentPath.readText(charset)
    }

    /**
     * 读取当前路径文件的字节内容
     * @return 文件的字节数组
     */
    fun readBytes(): ByteArray {
        return currentPath.readBytes()
    }

    /**
     * 将文本内容写入当前路径文件
     * @param text 要写入的文本内容
     * @param charset 文本编码，默认为UTF-8
     * @param options 文件打开选项
     */
    fun writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption) {
        currentPath.writeText(text, charset, *options)
    }

    /**
     * 将字节数组写入当前路径文件
     * @param array 要写入的字节数组
     * @param options 文件打开选项
     * ```
     */
    fun writeBytes(array: ByteArray, vararg options: OpenOption) {
        currentPath.writeBytes(array, *options)
    }

    fun writeLines(
        lines: Iterable<CharSequence>,
        charset: Charset = Charsets.UTF_8,
        vararg options: OpenOption
    ): PackPath {
        currentPath.writeLines(lines, charset, *options)
        return this
    }

    fun writeLines(
        lines: Sequence<CharSequence>,
        charset: Charset = Charsets.UTF_8,
        vararg options: OpenOption
    ): PackPath {
        currentPath.writeLines(lines, charset, *options)
        return this
    }

    fun writer(charset: Charset = Charsets.UTF_8, vararg options: OpenOption): OutputStreamWriter {
        return currentPath.writer(charset, *options)
    }

    fun bufferedWriter(
        charset: Charset = Charsets.UTF_8,
        bufferSize: Int = DEFAULT_BUFFER_SIZE,
        vararg options: OpenOption
    ): BufferedWriter {
        return currentPath.bufferedWriter(charset, bufferSize, *options)
    }

    /**
     * 判断当前路径是否为目录
     * @param options 链接选项
     * @return 如果是目录返回true，否则返回false
     */
    fun isDirectory(vararg options: LinkOption): Boolean {
        return currentPath.isDirectory(*options)
    }

    /**
     * 遍历当前路径下的所有文件和目录
     * @param options 遍历选项
     * @return 包含所有路径的序列
     */
    fun walk(vararg options: PathWalkOption): Sequence<Path> {
        return currentPath.walk(*options)
    }

    /**
     * 获取当前路径的绝对路径
     * @return 新的绝对路径
     */
    fun toAbsolutePath(): PackPath {
        return PackPath(this.absolutePathString)
    }

    /**
     * 将当前路径转换为绝对路径
     */
    fun absolutePath() {
        currentPath = currentPath.absolute()
    }

    /**
     * 将当前路径转换为File对象
     * @return 对应的File对象
     */
    fun toFile(): File {
        return currentPath.toFile()
    }


    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as PackPath

        return absolutePathString == other.absolutePathString
    }

    override fun hashCode(): Int {
        return currentPath.hashCode()
    }

    override fun toString(): String {
        return currentPath.toString()
    }

    /**
     * 将给定的路径转换为相对于当前路径的相对路径
     *
     * 示例：
     * ```
     * val basePath = PackPath("/home/user/documents")
     * val targetPath = PackPath("/home/user/documents/projects/file.txt")
     * val relativePath = basePath.relativize(targetPath)
     * println(relativePath.pathString) // 输出: projects/file.txt
     *
     * val basePath2 = PackPath("C:/Users/Admin")
     * val targetPath2 = PackPath("C:/Users/Admin/Documents/report.pdf")
     * val relativePath2 = basePath2.relativize(targetPath2)
     * println(relativePath2.pathString) // 输出: Documents/report.pdf
     * ```
     *
     * @param it 需要转换的PackPath对象
     * @return 转换后的相对路径PackPath对象，如果路径没有变化则返回原对象
     */
    fun relativize(it: PackPath): PackPath {
        var other = it.toPath
        var that = currentPath
        if (!other.isAbsolute) {
            other = other.toAbsolutePath()
        }
        if (!that.isAbsolute) {
            that = that.toAbsolutePath()
        }
        val path = that.relativize(other)
        if (other != path) {
            return PackPath(path)
        }
        return it
    }

    fun crc(): String {
        return Crc32.crc32Hex(this)
    }

    fun endsWith(key: String): Boolean {
        return currentPath.endsWith(key)
    }

    fun endsWith(key: PackPath): Boolean {
        return currentPath.endsWith(key.toPath)
    }

    fun startsWith(other: String): Boolean {
        return currentPath.startsWith(other)
    }

    fun startsWith(other: PackPath): Boolean {
        return currentPath.startsWith(other.toPath)
    }


}


/**
 * 扩展属性，将Path对象转换为PackPath对象
 *
 * @return PackPath对象，包装了当前Path实例
 */
val Path.toPackPath: PackPath
    get() = PackPath(this)

fun String.toPackPath(): PackPath {
    return PackPath(this)
}
